﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.InteropServices;

namespace Td.Additional.Runtime
{
    /// <summary>
    /// Class to support management of multiple application instances.
    /// </summary>
    public class InstanceManager : IDisposable
    {
        #region Private fields

        /// <summary>
        /// Internal application port for remoting
        /// </summary>
        private static String appPort = "AppPort";

        /// <summary>
        /// True, if resources are already disposed, otherwise false.
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// Internal mutex instance.
        /// </summary>
        private Mutex mutex = null;

        /// <summary>
        /// True, if app id is changed externally, otherwise false.
        /// </summary>
        private bool appIdChanged = false;

        #endregion

        #region Properties

        /// <summary>
        /// Instance manager (singleton)
        /// </summary>
        private static InstanceManager instanceManager;

        /// <summary>
        /// Gets the manager instance.
        /// </summary>
        /// <value>The manager instance.</value>
        public static InstanceManager ManagerInstance
        {
            get
            {
                if (instanceManager == null)
                    instanceManager = new InstanceManager();
                return instanceManager;
            }
        }

        private static Guid appId = Guid.Empty;

        /// <summary>
        /// Gets or sets the application guid.
        /// </summary>
        /// <value>The app id.</value>
        public Guid AppId
        {
            get { return appId; }
            private set { appId = value; }
        }

        /// <summary>
        /// Gets a value indicating whether another application instance is already running.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if another application instance is already running; otherwise, <c>false</c>.
        /// </value>
        public bool IsAlreadyRunning { get; private set; }

        #endregion

        #region Lifetime control

        /// <summary>
        /// Initializes a new instance of the <see cref="InstanceManager"/> class.
        /// </summary>
        private InstanceManager()
        {
            Assembly assembly = Assembly.GetEntryAssembly();
            object[] guids = assembly.GetCustomAttributes(typeof(GuidAttribute), false);
            if (guids == null || guids.Length == 0)
                throw new Exception("No Guid found in application attributes");
            SetApplicationId(new Guid(((GuidAttribute)guids[0]).Value));
            appIdChanged = false;
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="InstanceManager"/> is reclaimed by garbage collection.
        /// </summary>
        ~InstanceManager()
        {
            Dispose(false);
        }

        #endregion

        #region Private helper

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        private void Initialize()
        {
            bool isAlreadyRunning = false;
            if (mutex != null)
                mutex.Close();
            mutex = new Mutex(false, AppId.ToString(), out isAlreadyRunning);
            IsAlreadyRunning = !isAlreadyRunning;
            if (!IsAlreadyRunning)
            {
                ChannelServices.RegisterChannel(new IpcChannel(appPort), false);
                RemotingConfiguration.ApplicationName = AppId.ToString();
                RemotingConfiguration.RegisterWellKnownServiceType(
                   typeof(DataReceiver),
                   AppId.ToString(),
                   WellKnownObjectMode.SingleCall
                );
            }
            else
            {
                RemotingConfiguration.RegisterWellKnownClientType(
                   typeof(DataReceiver),
                   "ipc://" + appPort + "/" + AppId.ToString()
                );
                DataReceiver dataReceiver = new DataReceiver();
                dataReceiver.DataReceived(null);
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when [new instance created].
        /// </summary>
        public event EventHandler<EventArgs> NewInstanceCreated;

        /// <summary>
        /// Called when [new instance created].
        /// </summary>
        internal void OnNewInstanceCreated()
        {
            EventHandler<EventArgs> tempHandler = NewInstanceCreated;
            if (tempHandler != null)
                tempHandler(this, new EventArgs());
        }

        /// <summary>
        /// Occurs when [data received].
        /// </summary>
        public event EventHandler<DataReceivedEventArgs> DataReceived;

        /// <summary>
        /// Called when [data received].
        /// </summary>
        /// <param name="data">The data.</param>
        internal void OnDataReceived(byte[] data)
        {
            EventHandler<DataReceivedEventArgs> tempHandler = DataReceived;
            if (tempHandler != null)
                tempHandler(this, new DataReceivedEventArgs(data));
        }

        #endregion

        #region Member

        /// <summary>
        /// Sets the application id.
        /// </summary>
        /// <param name="guid">The GUID.</param>
        public void SetApplicationId(Guid guid)
        {
            if (appIdChanged)
                throw new Exception("Application id could not be set more than once.");
            AppId = guid;
            Initialize();
            appIdChanged = true;
        }

        /// <summary>
        /// Sends the specified data.
        /// </summary>
        /// <param name="data">The data.</param>
        public void Send(Byte[] data)
        {
            new DataReceiver().DataReceived(data);
        }

        /// <summary>
        /// Sends the specified data.
        /// </summary>
        /// <param name="data">The data.</param>
        public void Send(String data)
        {
            new DataReceiver().DataReceived(Encoding.ASCII.GetBytes(data));
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Gets a flag indicating if the object has been already disposed.
        /// </summary>
        protected bool Disposed
        {
            get { return disposed; }
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // Release resources here
                    if (mutex != null)
                    {
                        if (!IsAlreadyRunning) // First instance
                            mutex.ReleaseMutex();
                        mutex.Close();
                        mutex = null;
                    }
                }
                disposed = true;
            }
        }

        #endregion
    }
}